#GUI Controller
#This is the central module for managing the GUI. It contains 
#the thread used for updating the GUI as well. Pretty much
#anything that can be seen on the GUI was initialized here or passed through
#this module on its way to the graph or a table.
#
#Written and modified extensively by Nathan and Tony


from PyQt4 import QtCore, QtGui
import sys, subprocess, threading, time
import qgraph, qtable, qlist
import gui as gooey

class GUIC:
    def __init__(self, in_viewManager, in_cache):
        self.vm = in_viewManager
        self.cache = in_cache
        
        #initialize list of torrent names, various labels
        self.torrents = set(self.cache.get_torrent_names())
        self.tors_changed = True
        self.tor_dataLabels = ['Name', 'Upload Rate', 'Download Rate', 'Percent of Total Server Upload']
        self.tor_listLabels = ['Name', 'Upload Rate', 'Download Rate', 'Line']
        self.tor_keys = ['upload_payload', 'download_payload']
        self.serv_dataLabels = ['Active Torrents', 'Connected Clients', 'Upload Rate']
        self.serv_keys = ['num_torrents', 'num_peers','payload_upload_rate']
        self.peer_dataLabels = ['IP Address:Port', 'Client', 'Download Rate', 'Upload Rate', 'Progress']
        self.peer_keys = ['ip_addr', 'ip_port', 'client', 'payload_down_speed', 'payload_up_speed', 'progress']      
        
        #set default view server       
        self.views = []
        self.views.append(view_server())
        self.activeView = 0
        self.activeServKey = 2
        self.activeTorKey = 0
        self.drawMarkers = True
        
        self.torrentTable_model = qtable.DynamicTableModel(self.tor_dataLabels)
        self.peerTable_model = qtable.DynamicTableModel(self.peer_dataLabels)
        self.list_model = qlist.DynamicListModel(self.serv_dataLabels)
        
        self.app = QtGui.QApplication(sys.argv)
        self.window = QtGui.QMainWindow()
        self.ui = gooey.Ui_MainWindow()
        self.ui.setupUi(self.window)
        
        self.ui.tableView.setSortingEnabled(True)
        self.header = self.ui.tableView.horizontalHeader()
        self.header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.header.setCascadingSectionResizes(True)
        self.header.setStretchLastSection(True)

        self.graph = qgraph.DynamicCanvas() #initialize graph IMMEDIATELY before plugging it into Qt or BAD THINGS WILL HAPPEN       
        self.ui.graphSpace.addWidget(self.graph)
        self.ui.viewBox.addItem('Server')
        self.ui.tableView.setSelectionBehavior(1) #sets selection by rows for table
        self.ui.tableView.setModel(self.torrentTable_model)
        self.ui.serverList.setModel(self.list_model)
        
        #Testing Menu events
        self.window.connect(self.ui.actionStatic, QtCore.SIGNAL('activated()'), self.send_messages)
        self.window.connect(self.ui.actionDynamic, QtCore.SIGNAL('activated()'), self.send_message_dynamic)
        self.window.connect(self.ui.actionRealistic, QtCore.SIGNAL('activated()'), self.send_message_realistic)
        self.window.connect(self.ui.actionKill, QtCore.SIGNAL('activated()'), self.kill_send_script)
        
        #view changing events
        self.window.connect(self.ui.tableView, QtCore.SIGNAL('activated(QModelIndex)'), self.selected_torTable)
        self.window.connect(self.ui.viewBox, QtCore.SIGNAL('activated(int)'), self.change_view)

        
        #radio button events
        self.window.connect(self.ui.serv_bUpload, QtCore.SIGNAL('clicked()'), self.callback_radioS )
        self.window.connect(self.ui.serv_bPeers, QtCore.SIGNAL('clicked()'), self.callback_radioS )
        self.window.connect(self.ui.serv_bTors, QtCore.SIGNAL('clicked()'), self.callback_radioS )
        self.window.connect(self.ui.opt_markersOn, QtCore.SIGNAL('clicked()'), self.callback_radioOptions )
        self.window.connect(self.ui.opt_markersOff, QtCore.SIGNAL('clicked()'), self.callback_radioOptions )        
        
        #reference to the child process for sending test messages
        self.send_script = None
        
        self.lastServerUpRate = 0
        
        thread = threading.Thread(target=self.run)
        thread.daemon = True
        thread.start()
        
    def show_window(self):
        self.window.show()
        self.app.exec_()
        
    def run(self):
        """Central thread loop. Increments frame counter after each pass.
        """
        frames = 0
        frame_len = 1.0/3.0 #in seconds
        while(True):
            begin = time.time()
            
            if frames%6 == 0:
                self.update_torrents()
            
            self.update_activeGraph()
            self.update_list()

            if frames%4 == 0:
                if (self.is_serverView()):
                    self.update_torrentTable()
                elif (len(self.views[self.activeView].names) > 1):
                    self.update_torrentTable()
                else:
                    self.update_peerTable()            

            end = frame_len-(time.time()-begin)
            if end > 0.0: time.sleep(end)
            frames += 1

            
    def update_activeGraph(self):
        new = []
        
        if self.is_serverView():
            if (self.serv_keys[self.activeServKey] == 'payload_upload_rate'):
                update = graph_updateobj(self.vm.get_server_view(self.serv_keys[self.activeServKey], get_update=False), rate=True)
            else:
                update = graph_updateobj(self.vm.get_server_view(self.serv_keys[self.activeServKey], get_update=False))                
            new.append(update)
        else:
            for name in self.views[self.activeView].names:
                try:
                    update = graph_updateobj( self.vm.get_torrent_view(name,self.tor_keys[self.activeTorKey], get_update=False), rate=True )
                    new.append(update)
                except KeyError: pass
                
              
        if (self.graph.started == False):
            self.graph.compute_initial_figure(new, use_markers=self.drawMarkers)
            if self.is_serverView():
                label = self.serv_dataLabels[self.activeServKey]
                if (label == 'Upload Rate'): label += ' (kB/s)'
                self.graph.set_ylabel(label)
            else:
                label = self.tor_dataLabels[self.activeTorKey+1]
                if (label == 'Upload Rate'): label += ' (kB/s)'
                self.graph.set_ylabel(label)
            self.graph.set_xlabel("Time (HH:MM)")
            
        if (self.graph.started == True): self.graph.update_figure(new)
            
            
    def update_list(self):
        if self.list_model.started == True:
            self.refresh_list()
        
        else:
            if self.is_serverView():
                self.update_list_server()
            else:
                self.update_list_torrent()
            self.update_list()
                
    def update_list_server(self):
        new_prefixes = self.serv_dataLabels
        self.list_model.reset_list(new_prefixes)
                
    
    def update_list_torrent(self):
        new_prefixes = []
        for name in self.views[self.activeView].names:
            new_prefixes.extend(self.tor_listLabels + [""])
        self.list_model.reset_list(new_prefixes)            
            
    def update_torrents(self):
        compare = set(self.cache.get_torrent_names())
        if (compare == self.torrents): pass
        else:
            self.torrents = compare
            self.tors_changed = True
        
    def update_torrentTable(self):
        if self.tors_changed == False:
            self.refresh_torrentTable()
        else:
            self.torrentTable_model.update_rows(self.torrents)
            self.tors_changed = False
            self.update_torrentTable()
        
    def update_peerTable(self):
        ips = self.vm.get_peer_info(self.views[self.activeView].names, ['ip_addr', 'ip_port'])
        peers = []
        for peer in ips:
            ip = str(peer['ip_addr']) + ':' + str(peer['ip_port'])
            peers.append(ip)
        self.peerTable_model.update_rows(peers)
        self.refresh_peerTable()
        
    def refresh_list(self):
        new_entries = []
        
        if self.is_serverView():
            for key in self.serv_keys:
                entry = self.vm.get_server_view(key, get_update=True)[1]
                if (key == 'payload_upload_rate'): 
                    self.lastServerUpRate = entry
                    entry = self.format_rate(entry)
                new_entries.append(entry)
        else:
            for name in self.views[self.activeView].names:
                new_entries.append(name)
                for key in self.tor_keys:
                    entry = self.vm.get_torrent_view(name, key, get_update=True)[1]
                    new_entries.append(self.format_rate(entry))
                line = self.graph.theLines[self.views[self.activeView].names.index(name)]
                legend = line.get_color()
                legend += " " +line.get_linestyle()
                legend += " " +line.get_marker()
                new_entries.append(legend)
                if (name != self.views[self.activeView].names[-1]): new_entries.append("")
        self.list_model.refresh(new_entries)
            
        
    def refresh_peerTable(self):
        data = self.vm.get_peer_info(self.views[self.activeView].names, self.peer_keys)
        for peer in data:
            row = []
            ip = str(peer['ip_addr']) + ':' + str(peer['ip_port'])
            row.extend([ip, peer['client'], self.format_rate(peer['payload_down_speed']), self.format_rate(peer['payload_up_speed']), str(round(peer['progress'], 1)) + '%'])
            
            self.peerTable_model.refresh_row(row)
            
    def refresh_torrentTable(self):
        for name in self.torrents:
            new = [name]
            for key in self.tor_keys:
                try: new.append((self.vm.get_torrent_view(name,key,get_update=True)[1]))
                except KeyError: pass
        
            try: new.append(str(round(float(new[1]) / float(self.lastServerUpRate) * 100)) + "%")
            except ZeroDivisionError: new.append(str(0) + "%")
            new[1] = self.format_rate(new[1])
            new[2] = self.format_rate(new[2])
            self.torrentTable_model.refresh_row(new)
        
    def selected_torTable(self, index):
        if (not self.is_serverView()): 
            if (len(self.views[self.activeView].names) == 1): return    #signal came from peer table
        selected = self.ui.tableView.selectedIndexes()
        torrents = []
        if (type(selected) is list):
            for ind in selected:
                torr = str(self.torrentTable_model.rows[ind.row()][0])
                if (torr not in torrents):
                    torrents.append(torr)
            view = view_torrents(torrents)
            viewBoxInd = self.ui.viewBox.findText(view.label)
            
            #if view already exists, switch to it else add it to self.views
            if (viewBoxInd == -1):
                self.views.append(view)
                self.ui.viewBox.addItem(self.views[-1].label)
                self.change_view(len(self.views) - 1)
            else:
                self.change_view(viewBoxInd)
        
    def change_view(self, in_view):
        """Takes an integer corresponding to the viewList index
        you wish to switch to. 
        """
        self.graph.started = False
        self.list_model.started = False
        
        
        self.activeView = in_view
        self.ui.viewBox.setCurrentIndex(in_view)
        if (self.is_serverView()):
            self.ui.tableView.setModel(self.torrentTable_model)
            self.ui.list_label.setText("Server Status")
        elif (len(self.views[self.activeView].names) > 1):
            self.ui.tableView.setModel(self.torrentTable_model)
            self.ui.list_label.setText("Torrent Info")
        else:
            self.ui.tableView.setModel(self.peerTable_model)
            self.ui.list_label.setText("Torrent Info")
        
    #for testing on localhost
    def send_messages(self):
        subprocess.call("python send_messages.py 2000", shell=True)
        
    def send_message_dynamic(self):
        self.send_script = subprocess.Popen("python send_messages.py -d 2000", shell=True)
        
    def send_message_realistic(self):
        self.send_script = subprocess.Popen("python send_messages.py -r 6000", shell=True)
        
    def kill_send_script(self):
        if (self.send_script != None):
            self.send_script.kill()
        
    def is_serverView(self):
        if self.activeView == 0: return True
        else: return False
        
    def callback_radioS(self):
        if self.ui.serv_bUpload.isChecked():
            self.activeServKey=2
        elif self.ui.serv_bPeers.isChecked():
            self.activeServKey=1
        elif self.ui.serv_bTors.isChecked():
            self.activeServKey=0

        self.graph.started = False
        
    def callback_radioT(self):
        if self.ui.tor_bUpload.isChecked():
            self.activeTorKey=0
            return

    def callback_radioOptions(self):
        if self.ui.opt_markersOff.isChecked():
            if self.drawMarkers == True:
                self.drawMarkers = False
                self.graph.started = False
            return
        
        if self.ui.opt_markersOn.isChecked():
            if self.drawMarkers == False:
                self.drawMarkers = True
                self.graph.started = False
            return
        
    def format_rate(self, n):
        """Takes a transfer rate as a float or int and
        returns the rate in an appropriate unit of measurement
        with a matching suffix lable"""
        
        if (type(n) is list):
            return
        
        
        if (n < 1024):
            return str(n) + " B/s"
        elif (n < 1024000):
            return (str(int(round(float(n) / 1024))) + " kB/s") 
        elif (n < 1048576000):
            return (str(int(round(float(n) / 1048576))) + " MB/s")
        else:
            return (str(int(round(float(n) / 1073741824))) + "GB/s")
            
        
        
        
class graph_updateobj:
    """Construct with the x and y-values for one line,
    and pass the object to the graph's update methods. 
    """
    def __init__(self, in_xy, rate=False):
        self.x = in_xy[0]
        self.y = in_xy[1]
        self.rate = rate
        
class view_torrents:
    """Stores list of tracked names ([strings]) in self.names,
    and the view label (string) in self.label.
    Constructor([strings]) takes a list of torrent names,
    and makes a label from the first and last passed names.
    """
    def __init__(self, in_torrents):
        self.names = in_torrents
        self.label = in_torrents[0]+"--"+in_torrents[-1]
        
class view_server:
    """Should always be index 0 in the view list.
    """
    def __init__(self):
        self.label = "Server Overview"
